// https://panthema.net/2007/0328-ZLibString.html

// Copyright 2007 Timo Bingmann <tb@panthema.net>
// Distributed under the Boost Software License, Version 1.0.
// (See http://www.boost.org/LICENSE_1_0.txt)

#pragma once

#include <cstring>  // memset
#include <iomanip>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>

#include <zlib.h>

/** Decompress an STL string using zlib and return the original data. */
inline bool decompress_gzip_string(const std::string& str, std::string* outstring) {
  z_stream zs;  // z_stream is zlib's control structure
  memset(&zs, 0, sizeof(zs));

  if (inflateInit2(&zs, 16 + MAX_WBITS) != Z_OK) {
    *outstring = "inflateInit failed while decompressing.";
    return false;
  }

  zs.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(str.data()));
  zs.avail_in = str.size();

  int ret;
  char outbuffer[32768];

  // get the decompressed bytes blockwise using repeated calls to inflate
  do {
    zs.next_out = reinterpret_cast<Bytef*>(outbuffer);
    zs.avail_out = sizeof(outbuffer);

    ret = inflate(&zs, 0);

    if (outstring->size() < zs.total_out) {
      outstring->append(outbuffer, zs.total_out - outstring->size());
    }
  } while (ret == Z_OK);

  inflateEnd(&zs);

  if (ret != Z_STREAM_END) {  // an error occurred that was not EOF
    std::ostringstream oss;
    oss << "Exception during zlib decompression: (" << ret << ") " << zs.msg;
    *outstring = oss.str();
    return false;
  }

  return true;
}

inline bool deflate_string(const std::string& str, std::string* outstring) {
  z_stream zs;  // z_stream is zlib's control structure
  memset(&zs, 0, sizeof(zs));

  if (inflateInit2(&zs, -MAX_WBITS) != Z_OK) {
    *outstring = "inflateInit failed while decompressing.";
    return false;
  }

  zs.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(str.data()));
  zs.avail_in = str.size();

  int ret;
  char outbuffer[32768];

  // get the decompressed bytes blockwise using repeated calls to inflate
  do {
    zs.next_out = reinterpret_cast<Bytef*>(outbuffer);
    zs.avail_out = sizeof(outbuffer);

    ret = inflate(&zs, 0);

    if (outstring->size() < zs.total_out) {
      outstring->append(outbuffer, zs.total_out - outstring->size());
    }
  } while (ret == Z_OK);

  inflateEnd(&zs);

  if (ret != Z_STREAM_END) {  // an error occurred that was not EOF
    std::ostringstream oss;
    oss << "Exception during zlib decompression: (" << ret << ") " << zs.msg;
    *outstring = oss.str();
    return false;
  }

  return true;
}


inline std::string decompress_gzip_string(const std::string& str) {
  std::string outstring;
  bool ok = decompress_gzip_string(str, &outstring);
  if (!ok) {
    throw(std::runtime_error(outstring));
  }
  return outstring;
}

inline std::string deflate_string(const std::string& str) {
  std::string outstring;
  bool ok = deflate_string(str, &outstring);
  if (!ok) {
    throw(std::runtime_error(outstring));
  }
  return outstring;
}
